/* WebGL 3D models renderer
 *
 * Copyright (C) 2010   Nicolas Bonnel (nicolas.bonnel@gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Some parts of this program were inspired from this site : http://learningwebgl.com/blog/
 *
 */

var objects = [];
function load_binary_resource() {
	for (obj in objects)
		if(objects[obj].mesh)		
		{
			gl.deleteBuffer(objects[obj].mesh.vertexPositionBuffer);
			gl.deleteBuffer(objects[obj].mesh.normalsBuffer);
			gl.deleteBuffer(objects[obj].mesh.vertexTextureCoordBuffer);
			gl.deleteBuffer(objects[obj].mesh.elementBuffer);
			gl.uniform1i(shaderProgram.useTexturesUniform, false);
		}
	objects = [];
	
	clearConsole();
	var url = document.getElementById("modelURL").value;
        info(url);
	var req = new XMLHttpRequest();
	req.open('GET', url, true);
	// The following line says we want to receive data as Binary and not as Unicode
	req.overrideMimeType('text/plain; charset=x-user-defined');
	req.onreadystatechange = function() { 
		info("Ready state : "+req.readyState+", file size : "+req.responseText.length);
		
		if(req.readyState == 4){
			if (req.responseText.length>0){
				var parsed = parseFile(req.responseText,url.substring(0,url.lastIndexOf('/')+1));
				var texts = [];
				for(te in parsed.textures)
					texts.push(initTexture(parsed.textures[te]));
				for (ob in parsed.objs)
					if (parsed.objs[ob].mesh){
						initBuffer(parsed.objs[ob].mesh);
						parsed.objs[ob].textures = texts;
					}
				objects = parsed.objs;
			} 
		}
	}
	req.send(null);
	//if (req.status != 200) return '';
	//return req.responseText.length;
};

var gl;
function initGL(canvas) {
	try {
		gl = canvas.getContext("experimental-webgl");
		gl.viewportWidth = canvas.width;
		gl.viewportHeight = canvas.height;
	} catch(e) {
	}
	if (!gl) 
		error("Could not initialise WebGL, sorry :-(");
}


function getShader(gl, id) {
	var shaderScript = document.getElementById(id);
	if (!shaderScript)
		return null;
	var str = "";
    	var k = shaderScript.firstChild;
    	while (k) {
      		if (k.nodeType == 3)
        		str += k.textContent;
      		k = k.nextSibling;
    	}
	var shader;
    	if (shaderScript.type == "x-shader/x-fragment")
      		shader = gl.createShader(gl.FRAGMENT_SHADER);
    	else if (shaderScript.type == "x-shader/x-vertex")
      		shader = gl.createShader(gl.VERTEX_SHADER);
	else
      		return null;
	gl.shaderSource(shader, str);
	gl.compileShader(shader);
	if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)){
      		error(gl.getShaderInfoLog(shader));
      		return null;
    	}
	return shader;
}

var shaderProgram;
function initShaders() {
    	var fragmentShader = getShader(gl, "shader-fs");
    	var vertexShader = getShader(gl, "shader-vs");

    	shaderProgram = gl.createProgram();
    	gl.attachShader(shaderProgram, vertexShader);
    	gl.attachShader(shaderProgram, fragmentShader);
    	gl.linkProgram(shaderProgram);

    	if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS))
      		error("Could not initialise shaders");

    	gl.useProgram(shaderProgram);

    	shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
    	gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);

	shaderProgram.normalsAttribute = gl.getAttribLocation(shaderProgram, "aNormals");
    	gl.enableVertexAttribArray(shaderProgram.normalsAttribute);

    	shaderProgram.textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord");

    	shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
    	shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
	shaderProgram.nMatrixUniform = gl.getUniformLocation(shaderProgram, "uNMatrix");
    	shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler");
	shaderProgram.useTexturesUniform = gl.getUniformLocation(shaderProgram, "uUseTextures");
	shaderProgram.ambiantLight = gl.getUniformLocation(shaderProgram, "uAmbiantLight");
	shaderProgram.directionnalLight = gl.getUniformLocation(shaderProgram, "uDirectionnalLight");
}


function handleLoadedTexture(texture) {
	gl.bindTexture(gl.TEXTURE_2D, texture);
	gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
	gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image);
	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
   	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
	gl.bindTexture(gl.TEXTURE_2D, null);
}

function initTexture(path) {
	var neheTexture;
	neheTexture = gl.createTexture();
	neheTexture.image = new Image();
	neheTexture.image.src = path;
	neheTexture.image.onload = function() {
		handleLoadedTexture(neheTexture);
		gl.uniform1i(shaderProgram.useTexturesUniform, true);
		info("Init texture, size : "+neheTexture.image.width+" x "+neheTexture.image.height);
	}
	return neheTexture;
}


var mvMatrix;
var mvMatrixStack = [];

function mvPushMatrix(m) {
    	if(m){
      		mvMatrixStack.push(m.dup());
      		mvMatrix = m.dup();
    	}else
     	 	mvMatrixStack.push(mvMatrix.dup());
}

function mvPopMatrix() {
    	if (mvMatrixStack.length == 0)
		throw "Invalid popMatrix!";
	mvMatrix = mvMatrixStack.pop();
	return mvMatrix;
}

function loadIdentity() {
	mvMatrix = Matrix.I(4);
}

function multMatrix(m) {
	mvMatrix = mvMatrix.x(m);
}

function mvTranslate(v) {
    	var m = Matrix.Translation($V([v[0], v[1], v[2]])).ensure4x4();
    	multMatrix(m);
}

function mvRotate(ang, v) {
    	var arad = ang * Math.PI / 180.0;
    	var m = Matrix.Rotation(arad, $V([v[0], v[1], v[2]])).ensure4x4();
    	multMatrix(m);
}

var pMatrix;
  	function perspective(fovy, aspect, znear, zfar) {
    	pMatrix = makePerspective(fovy, aspect, znear, zfar);
}

function setMatrixUniforms() {
    	gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, new Float32Array(pMatrix.flatten()));
   	gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, new Float32Array(mvMatrix.flatten()));
	var normalMatrix = mvMatrix.inverse();
	normalMatrix = normalMatrix.transpose();
	gl.uniformMatrix4fv(shaderProgram.nMatrixUniform, false, new Float32Array(normalMatrix.flatten()));
}


function initBuffer(mesh) {
	//var square = [-4.0,-4.0,0.0,4.0,-4.0,0.0,-4.0,4.0,0.0,4.0,4.0,0.0,4.0,-4.0,0.0,-4.0,4.0,0.0]
	mesh.vertexPositionBuffer = gl.createBuffer();
	gl.bindBuffer(gl.ARRAY_BUFFER, mesh.vertexPositionBuffer);
	gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(mesh.vertices), gl.STATIC_DRAW);
    	mesh.vertexPositionBuffer.itemSize = 3;
    	mesh.vertexPositionBuffer.numItems = (mesh.vertices.length)/3;

	mesh.normalsBuffer = gl.createBuffer();
	gl.bindBuffer(gl.ARRAY_BUFFER, mesh.normalsBuffer);
	gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(mesh.normals), gl.STATIC_DRAW);
    	mesh.normalsBuffer.itemSize = 3;
    	mesh.normalsBuffer.numItems = (mesh.normals.length)/3;

	//var tex = [0.0,0.0,1.0,0.0,0.0,1.0,1.0,1.0,1.0,0.0,0.0,1.0]
	mesh.vertexTextureCoordBuffer = gl.createBuffer();
	gl.bindBuffer(gl.ARRAY_BUFFER, mesh.vertexTextureCoordBuffer);
	if (mesh.textures.length>0){
		gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute);
		gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(mesh.textures), gl.STATIC_DRAW);
	   	mesh.vertexTextureCoordBuffer.itemSize = 2;
		mesh.vertexTextureCoordBuffer.numItems = mesh.textures.length/2;
	}else
		gl.disableVertexAttribArray(shaderProgram.textureCoordAttribute);

	mesh.elementBuffer = gl.createBuffer();
	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, mesh.elementBuffer);
	gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(mesh.faces), gl.STATIC_DRAW);

    	mesh.elementBuffer.itemSize = 1;
    	mesh.elementBuffer.numItems = (mesh.faces.length);


  }


var viewDist = -10;
var rTri = 0;
function drawScene() {
    	gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
    	gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    	perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0);
    	loadIdentity();
	mvTranslate([0.0, 0.0, viewDist]);
	mvRotate(-70, [1, 0, 0]);
	gl.uniform3f(shaderProgram.ambiantLight,0.2,0.2,0.2);
	gl.uniform3f(shaderProgram.directionnalLight,0.8,0.8,0.8);
	for (obj in objects)
	if(objects[obj].mesh){
		//mvTranslate([2.0, 0.0, 0.0]);
		mvPushMatrix();
		mvRotate(rTri, [0, 0, 1]);
		gl.bindBuffer(gl.ARRAY_BUFFER,objects[obj].mesh.vertexPositionBuffer);
	    	gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,objects[obj].mesh.vertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
		gl.bindBuffer(gl.ARRAY_BUFFER,objects[obj].mesh.normalsBuffer);
	    	gl.vertexAttribPointer(shaderProgram.normalsAttribute, objects[obj].mesh.normalsBuffer.itemSize, gl.FLOAT, false, 0, 0);

		gl.bindBuffer(gl.ARRAY_BUFFER, objects[obj].mesh.vertexTextureCoordBuffer);
		if (objects[obj].mesh.textures.length>0){
			gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute);
			gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, objects[obj].mesh.vertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);
			for(i=0;i<objects[obj].textures.length;i++){
				gl.activeTexture(gl.TEXTURE0+i);
				gl.bindTexture(gl.TEXTURE_2D, objects[obj].textures[i]);
				gl.uniform1i(shaderProgram.samplerUniform, 0);
			}
		}else
			gl.disableVertexAttribArray(shaderProgram.textureCoordAttribute);

	    	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, objects[obj].mesh.elementBuffer);

	    	setMatrixUniforms();
		//gl.drawArrays(gl.TRIANGLES, 0, 6);
	    	gl.drawElements(gl.TRIANGLES,objects[obj].mesh.elementBuffer.numItems, gl.UNSIGNED_SHORT, 0);
		
		mvPopMatrix();
		
	}

  }


  var lastTime = 0;
  function animate() {
    var timeNow = new Date().getTime();
    if (lastTime != 0) {
      var elapsed = timeNow - lastTime;

      rTri += (90 * elapsed) / 2000.0;
    }
    lastTime = timeNow;
  }


  function tick() {
    drawScene();
    animate();
  }


function webGLStart() {
	var canvas = document.getElementById(".blend viewer");
	initGL(canvas);
	initShaders();
    	gl.clearColor(0.0, 0.0, 0.0, 1.0);
    	gl.clearDepth(1.0);
   	gl.enable(gl.DEPTH_TEST);
    	gl.depthFunc(gl.LEQUAL);
	setInterval(tick, 15);
	//load();
}

/** This is high-level function.
 * It must react to delta being more/less than zero.
 */
function handle(delta) {
        if (delta < 0)
		viewDist--;
        else
		viewDist++;
}

/** Event handler for mouse wheel event.
 */
function wheel(event){
        var delta = 0;
        if (!event) /* For IE. */
                event = window.event;
        if (event.wheelDelta) { /* IE/Opera. */
                delta = event.wheelDelta/120;
                /** In Opera 9, delta differs in sign as compared to IE.
                 */
                if (window.opera)
                        delta = -delta;
        } else if (event.detail) { /** Mozilla case. */
                /** In Mozilla, sign of delta is different than in IE.
                 * Also, delta is multiple of 3.
                 */
                delta = -event.detail/3;
        }
        /** If delta is nonzero, handle it.
         * Basically, delta is now positive if wheel was scrolled up,
         * and negative, if wheel was scrolled down.
         */
        if (delta)
                handle(delta);
        /** Prevent default actions caused by mouse wheel.
         * That might be ugly, but we handle scrolls somehow
         * anyway, so don't bother here..
         */
        if (event.preventDefault)
                event.preventDefault();
	event.returnValue = false;
}

/** Initialization code. 
 * If you use your own event management code, change it as required.
 */
if (window.addEventListener)
        /** DOMMouseScroll is for mozilla. */
        window.addEventListener('DOMMouseScroll', wheel, false);
/** IE/Opera. */
window.onmousewheel = document.onmousewheel = wheel;
